package xin.jeasy.mybatis.generator.plugin;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.mybatis.generator.api.FullyQualifiedTable;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.IntrospectedTable.TargetRuntime;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.java.PrimitiveTypeWrapper;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.*;


public class LimitForMysqlPagePlugin extends PluginAdapter {

    private Map<FullyQualifiedTable, List<XmlElement>> elementsToAdd;

    public LimitForMysqlPagePlugin() {
        elementsToAdd = new HashMap<FullyQualifiedTable, List<XmlElement>>();
    }

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    @Override
    public boolean clientSelectByExampleWithBLOBsMethodGenerated(Method method, Interface interfaze,
                                                                 IntrospectedTable introspectedTable) {
        if (introspectedTable.getTargetRuntime() == TargetRuntime.MYBATIS3) {
            copyAndAddMethod(method, interfaze);
        }
        return true;
    }

    @Override
    public boolean clientSelectByExampleWithoutBLOBsMethodGenerated(Method method, Interface interfaze,
                                                                    IntrospectedTable introspectedTable) {
        if (introspectedTable.getTargetRuntime() == TargetRuntime.MYBATIS3) {
            copyAndAddMethod(method, interfaze);
        }
        return true;
    }

    @Override
    public boolean sqlMapSelectByExampleWithoutBLOBsElementGenerated(XmlElement element,
                                                                     IntrospectedTable introspectedTable) {
        if (introspectedTable.getTargetRuntime() == TargetRuntime.MYBATIS3) {
            copyAndSaveElement(element, introspectedTable);
        }
        return true;
    }

    @Override
    public boolean sqlMapSelectByExampleWithBLOBsElementGenerated(XmlElement element,
                                                                  IntrospectedTable introspectedTable) {
        if (introspectedTable.getTargetRuntime() == TargetRuntime.MYBATIS3) {
            copyAndSaveElement(element, introspectedTable);
        }
        return true;
    }

    /**
     * We'll override this method and add any new elements generated by previous
     * calls
     */
    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {

        List<XmlElement> elements = elementsToAdd.get(introspectedTable.getFullyQualifiedTable());
        if (elements != null) {
            for (XmlElement element : elements) {
                document.getRootElement().addElement(element);
            }
        }

        return true;
    }

    /**
     * Use the method copy constructor to create a new method
     */
    private void copyAndAddMethod(Method method, Interface interfaze) {
        Method newMethod = new Method(method);
        newMethod.setName(method.getName() + "WithLimit");
        interfaze.addMethod(newMethod);
    }

    /**
     * Use the method copy constructor to create a new element
     */
    private void copyAndSaveElement(XmlElement element, IntrospectedTable introspectedTable) {
        FullyQualifiedTable fqt = introspectedTable.getFullyQualifiedTable();
        XmlElement newElement = new XmlElement(element);

        // remove old id attribute and add a new one with the new name
        for (Iterator<Attribute> iterator = newElement.getAttributes().iterator(); iterator.hasNext(); ) {
            Attribute attribute = iterator.next();
            if ("id".equals(attribute.getName())) {
                iterator.remove();
                Attribute newAttribute = new Attribute("id", attribute.getValue() + "WithLimit");
                newElement.addAttribute(newAttribute);
                Element element1 = new Element() {
                    @Override
                    public String getFormattedContent(int indentLevel) {
                        return "\tselect t.* from ( ";
                    }
                };
                newElement.addElement(0, element1);


                XmlElement ifLimitNotNullElement = new XmlElement("if");
                ifLimitNotNullElement.addAttribute(new Attribute("test", "limit != null"));

                XmlElement ifOffsetNotNullElement = new XmlElement("if");
                ifOffsetNotNullElement.addAttribute(new Attribute("test", "offset != null"));
                ifOffsetNotNullElement.addElement(new TextElement("limit ${offset}, ${limit}"));
                ifLimitNotNullElement.addElement(ifOffsetNotNullElement);

                XmlElement ifOffsetNullElement = new XmlElement("if");
                ifOffsetNullElement.addAttribute(new Attribute("test", "offset == null"));
                ifOffsetNullElement.addElement(new TextElement("limit ${limit}"));
                ifLimitNotNullElement.addElement(ifOffsetNullElement);

                newElement.addElement(ifLimitNotNullElement);

                Element element2 = new Element() {
                    @Override
                    public String getFormattedContent(int indentLevel) {
                        return "\t ) a, " + fqt.getFullyQualifiedTableNameAtRuntime() + " t\n" +
                                "\t where a." + introspectedTable.getPrimaryKeyColumns().get(0).getActualColumnName()
                                + " = t." + introspectedTable.getPrimaryKeyColumns().get(0).getActualColumnName() + "";
                    }
                };

                newElement.addElement(element2);
                break;
            }
        }

        // save the new element locally. We'll add it to the document
        // later
        List<XmlElement> elements = elementsToAdd.get(fqt);
        if (elements == null) {
            elements = new ArrayList<XmlElement>();
            elementsToAdd.put(fqt, elements);
        }
        elements.add(newElement);

    }

    /**
     * 为每个Example类添加limit和offset属性以及set、get方法
     */
    @Override
    public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

        PrimitiveTypeWrapper integerWrapper = FullyQualifiedJavaType.getIntInstance().getPrimitiveTypeWrapper();

        Field limit = new Field();
        limit.setName("limit");
        limit.setVisibility(JavaVisibility.PRIVATE);
        limit.setType(integerWrapper);
        topLevelClass.addField(limit);

        Method setLimit = new Method();
        setLimit.setVisibility(JavaVisibility.PUBLIC);
        setLimit.setName("setLimit");
        setLimit.addParameter(new Parameter(integerWrapper, "limit"));
        setLimit.addBodyLine("this.limit = limit;");
        topLevelClass.addMethod(setLimit);

        Method getLimit = new Method();
        getLimit.setVisibility(JavaVisibility.PUBLIC);
        getLimit.setReturnType(integerWrapper);
        getLimit.setName("getLimit");
        getLimit.addBodyLine("return limit;");
        topLevelClass.addMethod(getLimit);

        Field offset = new Field();
        offset.setName("offset");
        offset.setVisibility(JavaVisibility.PRIVATE);
        offset.setType(integerWrapper);
        topLevelClass.addField(offset);

        Method setOffset = new Method();
        setOffset.setVisibility(JavaVisibility.PUBLIC);
        setOffset.setName("setOffset");
        setOffset.addParameter(new Parameter(integerWrapper, "offset"));
        setOffset.addBodyLine("this.offset = offset;");
        topLevelClass.addMethod(setOffset);

        Method getOffset = new Method();
        getOffset.setVisibility(JavaVisibility.PUBLIC);
        getOffset.setReturnType(integerWrapper);
        getOffset.setName("getOffset");
        getOffset.addBodyLine("return offset;");
        topLevelClass.addMethod(getOffset);

        return true;
    }
}
